package Person;
 
use strict;
use warnings;
 
use Carp qw( confess );
use DateTime;
use DateTime::Format::Natural;
 
sub new {
    my $class = shift;
    my %p = ref $_[0] ? %{ $_[0] } : @_;
 
    exists $p{name}
        or confess 'name is a required attribute';
    $class->_validate_name( $p{name} );
 
    exists $p{birth_date}
        or confess 'birth_date is a required attribute';
 
    $p{birth_date} = $class->_coerce_birth_date( $p{birth_date} );
    $class->_validate_birth_date( $p{birth_date} );
 
    $p{shirt_size} = 'l'
        unless exists $p{shirt_size}:
 
    $class->_validate_shirt_size( $p{shirt_size} );
 
    return bless \%p, $class;
}
 
sub _validate_name {
    shift;
    my $name = shift;
 
    local $Carp::CarpLevel = $Carp::CarpLevel + 1;
 
    defined $name
        or confess 'name must be a string';
}
 
{
    my $en_parser = DateTime::Format::Natural->new(
        lang      => 'en',
        time_zone => 'UTC',
    );
 
    sub _coerce_birth_date {
        shift;
        my $date = shift;
 
        return $date unless defined $date && ! ref $date;
 
        my $dt = $en_parser->parse_datetime($date);
 
        return $dt ? $dt : undef;
    }
}
 
sub _validate_birth_date {
    shift;
    my $birth_date = shift;
 
    local $Carp::CarpLevel = $Carp::CarpLevel + 1;
 
    $birth_date->isa('DateTime')
        or confess 'birth_date must be a DateTime object';
}
 
sub _validate_shirt_size {
    shift;
    my $shirt_size = shift;
 
    local $Carp::CarpLevel = $Carp::CarpLevel + 1;
 
    defined $shirt_size
        or confess 'shirt_size cannot be undef';
 
    my %sizes = map { $_ => 1 } qw( s m l xl xxl );
 
    $sizes{$shirt_size}
        or confess "$shirt_size is not a valid shirt size (s, m, l, xl, xxl)";
}
 
sub name {
    my $self = shift;
 
    if (@_) {
        $self->_validate_name( $_[0] );
        $self->{name} = $_[0];
    }
 
    return $self->{name};
}
 
sub birth_date {
    my $self = shift;
 
    if (@_) {
        my $date = $self->_coerce_birth_date( $_[0] );
        $self->_validate_birth_date( $date );
 
        $self->{birth_date} = $date;
    }
 
    return $self->{birth_date};
}
 
sub birth_year {
    my $self = shift;
 
    return $self->birth_date->year;
}
 
sub shirt_size {
    my $self = shift;
 
    if (@_) {
        $self->_validate_shirt_size( $_[0] );
        $self->{shirt_size} = $_[0];
    }
 
    return $self->{shirt_size};
}